
package roborally.model;

import java.util.HashSet;
import java.util.Set;

import roborally.IFacade;
import roborally.exceptions.IllegalMoveException;
import roborally.exceptions.InvalidPositionException;
import roborally.exceptions.PositionSharingException;

/**
 * A class for connecting the RoboRally-code with the user interface.
 * 
 * @version  1.0
 * @author  Pieter Robberechts	(1e Bach Informatica)
 * @author	Jeroen Sanders		(1e Bach Informatica)
 *  
 */
public class Facade implements IFacade<Board, Robot, Wall, Battery> {

	/**
	 * Create a new board with the given width and height. 
	 * 	Return null if the given width or height is invalid.
	 * 
	 * @param	width
	 * 			The width for the new board.
	 * @param	height
	 * 			The height for the new board.
	 * @return	...
	 * 			| result == new Board(width, height)
	 */
	@Override
	public Board createBoard(long width, long height) {
		try{
			return new Board(width, height);
		}catch(IllegalArgumentException e){
			System.err.println(e.getMessage());
		}
		return null;
	}

	/**
	 * Merge board 1 and board 2.
	 * 
	 * @param	board1
	 * 			The first board to merge.
	 * @param	board2
	 * 			The second board to merge.
	 * @effect	...
	 * 			| board1.merge(board2)
	 */
	@Override
	public void merge(Board board1, Board board2) {
		try{
			board1.merge(board2);
		}catch(IllegalArgumentException e){
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Create a new battery with given initial energy and weight. 
	 * 	Return null if the given parameters are invalid (e.g. negative weight). 
	 * 
	 * @param	initialEnergy
	 * 			The initial amount of energy for this new battery.
	 * @param	weight
	 * 			The weight for this new battery.
	 * @return	...
	 * 			| result == new Battery(new EnergyAmount(initialEnergy), new Weight(weight))
	 */
	@Override
	public Battery createBattery(double initialEnergy, int weight) {
		Battery battery = new Battery(new EnergyAmount(initialEnergy), new Weight(weight));
		if(battery.canHaveAsEnergy(new EnergyAmount(initialEnergy)) && battery.canHaveAsWeight(new Weight(weight)))	
			return battery;
		else
			return null;
	}

	/**
	 * Put a battery at a given position, on a given board (if possible).
	 * 
	 * @param	board
	 * 			The board to put the battery on.
	 * @param	x
	 * 			The x-coordinate of the position to put the battery at.
	 * @param	y
	 * 			The y-coordinate of the position to put the battery at.
	 * @param	battery
	 * 			The battery to put on a given board, at a given position.
	 * @effect	...
	 * 			| board.putElement(new Position(x,y), battery)
	 */
	@Override
	public void putBattery(Board board, long x, long y, Battery battery) {
		try {
			board.putElement(new Position(x,y), battery);
		} catch(IllegalStateException e) {
			System.err.println(e.getMessage());
		} catch (IllegalArgumentException e) {
			System.err.println(e.getMessage());
		} catch (InvalidPositionException e) {
			System.err.println(e.getMessage());
		} catch (PositionSharingException e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Return the x-coordinate of a given battery.
	 * 
	 * @param 	battery
	 * 			The battery which x-coordinate should be returned.
	 * @return 	...
	 * 			| if(battery.getPosition() != null)
	 * 			|	then result == battery.getPosition().getX()
	 * 			| else
	 * 			|	then result == -1
	 */
	@Override
	public long getBatteryX(Battery battery) {
		if(battery.getPosition() == null) {
			System.err.println("The battery is not placed on a board!");
			return -1;
		}	
		return battery.getPosition().getX();	
	}

	/**
	 * Return the y-coordinate of a given battery.
	 * 
	 * @param 	battery
	 * 			The battery which y-coordinate should be returned.
	 * @return 	...
	 * 			| if(battery.getPosition() != null)
	 * 			|	then result == battery.getPosition().getY()
	 * 			| else
	 * 			|	then result == -1
	 */
	@Override
	public long getBatteryY(Battery battery) {
		if(battery.getPosition() == null) {
			System.err.println("The battery is not placed on a board!");
			return -1;
		}
		return battery.getPosition().getY();	
	}

	/** 
	 * Create a new Robot with a given orientation and energy.
	 * 	Return null if the given parameters are invalid (e.g. negative energy). 
	 * 
	 * @param	orientation
	 *  		The initial orientation for this new robot.
	 * @param	initialEnergy
	 *  		The initial amount of energy for this new robot.
	 * @return	...
	 * 			| result == new Robot(Orientation.getConstant(orientation), new EnergyAmount(initialEnergy));
	 */
	@Override
	public Robot createRobot(int orientation, double initialEnergy) {
		Orientation orientationConstant = Orientation.getConstant(orientation);
		EnergyAmount energyAmount = new EnergyAmount(initialEnergy);
		Robot robot = new Robot(orientationConstant, energyAmount);
		
		if(robot.canHaveAsOrientation(orientationConstant) && robot.canHaveAsEnergy(energyAmount))
			return robot;
		else
			return null;
	}

	/**
	 * Put a given robot at a given position, on a given board (if possible).
	 * 
	 * @param	board
	 * 			The board to put the robot on.
	 * @param	x
	 * 			The x-coordinate of the position to put the robot at.
	 * @param	y
	 * 			The y-coordinate of the position to put the robot at.
	 * @param	robot
	 * 			The robot to put on a given position, on a given board.
	 * @effect	...
	 * 			| board.putElement(new Position(x,y), robot) 
	 */
	@Override
	public void putRobot(Board board, long x, long y, Robot robot) {
		try {
			board.putElement(new Position(x,y), robot);
		} catch (IllegalStateException e) {
			System.err.println(e.getMessage());
		} catch (IllegalArgumentException e) {
			System.err.println(e.getMessage());
		} catch (InvalidPositionException e) {
			System.err.println(e.getMessage());
		} catch (PositionSharingException e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Return the x-coordinate of a given robot.
	 * 
	 * @param 	robot
	 * 			The robot which x-coordinate should be returned.
	 * @return 	...
	 * 			| if(robot.getPosition() != null)
	 * 			|	then result == robot.getPosition().getX()
	 * 			| else
	 * 			|	then result == -1
	 */
	@Override
	public long getRobotX(Robot robot) {
		if(robot.getPosition() == null) {
			System.err.println("The robot is not placed on a board!");
			return -1;
		}
		return robot.getPosition().getX();
	}

	/**
	 * Return the y-coordinate of a given robot.
	 * 
	 * @param 	robot
	 * 			The robot which y-coordinate should be returned.
	 * @return 	...
	 * 			| if(robot.getPosition() != null)
	 * 			|	then result == robot.getPosition().getY()
	 * 			| else
	 * 			|	then result == -1
	 */
	@Override
	public long getRobotY(Robot robot) {
		if(robot.getPosition() == null) {
			System.err.println("The robot is not placed on a board!");
			return -1;
		}
		return robot.getPosition().getY();
	}

	/**
	 * Return the orientation (either 0, 1, 2 or 3) of a given robot.
	 * 
	 * @param 	robot
	 * 			The robot which orientation should be returned.
	 * @return	...
	 * 			| result == robot.getOrientation().getNumber()
	 */
	@Override
	public int getOrientation(Robot robot) {
		return robot.getOrientation().getNumber();
	}

	/**
	 * Return the current amount of energy in watt-second of a given robot.
	 * 
	 * @param 	robot
	 * 			The robot which amount of energy should be returned.
	 * @return	...
	 * 			| result == robot.getEnergy().getAmountInUnits()
	 */
	@Override
	public double getEnergy(Robot robot) {
		return robot.getEnergy().getAmountInUnits();
	}

	/**
	 * Move a given robot one step in its current direction if the robot has sufficient energy. 
	 * 	Do not modify the state of the robot if it has insufficient energy.
	 * 
	 * @param 	robot
	 * 			The robot which should be moved.
	 * @effect	...
	 * 			| if(! robot.getEnergy().lessThan(robot.getEnergyRequiredToMove()) )
	 * 			| 	then robot.move()
	 */	
	@Override
	public void move(Robot robot) {
		if(! robot.getEnergy().lessThan(robot.getEnergyRequiredToMove()) ) {
			try {
				robot.move();
			} catch (IllegalMoveException e) {
				System.err.println(e.getMessage());
			} catch (IllegalStateException e) {
				System.err.println(e.getMessage());
			}
		}
	}

	/**
	 * Turn a given robot 90 degrees in clockwise direction if the robot has sufficient energy. 
	 * 	Do not modify the state of the robot if it has insufficient energy.
	 * 
	 * @param 	robot
	 * 			The robot which should be turned.
	 * @efect	...
	 * 			| if(! robot.getEnergy().lessThan(Robot.getEnergyRequiredToTurn()))	
	 * 			| 	then robot.turn(Direction.CLOCKWISE)
	 */
	@Override
	public void turn(Robot robot) {
		if(! robot.getEnergy().lessThan(Robot.getEnergyRequiredToTurn())) {
			robot.turn(Direction.CLOCKWISE);
		}
	}

	/**
	 * Return the set of batteries that a given robot is carrying.
	 * 
	 * @param	robot
	 * 			The robot from which the set of batteries it is carrying should be returned.
	 * @return	...
	 * 			| for each item in robot.getItems() :
	 * 			| 	if(item instanceof Battery)
	 * 			|		then result == item
	 */
	@Override
	public Set<Battery> getPossessions(Robot robot) {
		Set<Battery> possession = new HashSet<Battery>();
		for(Item item : robot.getItems())
			try{
				possession.add((Battery) item);
			}catch(ClassCastException e){}
		return possession;
	}

	/**
	 * Make a given robot pick up a given battery (if possible).
	 * 
	 * @param	robot
	 * 			The robot which should pick up the given battery.
	 * @param	battery
	 * 			The battery which should be picked up.
	 * @effect	...
	 * 			| robot.pickUp(battery)
	 */
	@Override
	public void pickUp(Robot robot, Battery battery) {
		robot.pickUp(battery);
	}

	/**
	 * Make a given robot use a given battery (if possible).
	 * 
	 * @param	robot
	 * 			The robot which should use the given battery.
	 * @param	battery
	 * 			The battery which should be used.
	 * @effect	...
	 * 			| robot.use(battery)
	 */
	@Override
	public void use(Robot robot, Battery battery) {
		robot.use(battery);		
	}

	/**
	 * Make a given robot drop a given battery (if possible).
	 * 
	 * @param	robot
	 * 			The robot which should drop the given battery.
	 * @param	battery
	 * 			The battery which should be dropped.
	 * @effect	...
	 * 			| robot.drop(battery)
	 */
	@Override
	public void drop(Robot robot, Battery battery) {
		robot.drop(battery);		
	}

	/**
	 * Return whether your implementation of <code>isMinimalCostToReach</code> takes into account other robots, walls and turning (required to score 17+). The return
	 * value of this method determines the expected return value of <code>isMinimalCostToReach</code> in the test suite.
	 * 
	 * This method must return either 0 or 1.
	 */
	@Override
	public int isMinimalCostToReach17Plus() {
		return 1;
	}

	/**
	 * Return the minimal amount of energy required for a given robot to reach a position (x, y),
	 * taking into account the robot's current load and energy level.
	 * 
	 * @param	robot
	 * 			The robot for which the minimal cost to reach the given position should be calculated.
	 * @param	x
	 * 			The x-coordinate of the position for which the minimal cost to reach it with the given robot should be calculated.
	 * @param	y
	 * 			The y-coordinate of the position for which the minimal cost to reach it with the given robot should be calculated.
	 * @return	Return -1 if robot is not placed on a board.
	 * @return	Return -2 if the robot has insufficient energy to reach (x, y).
	 */
	@Override
	public double getMinimalCostToReach(Robot robot, long x, long y) {
		try {
			return robot.getMinimalCostToReach(new Position(x,y));
		} catch (Exception e) {
			return -1;
		}
	}
	
	/**
	 * Return whether your implementation of <code>moveNextTo</code> takes into account other robots, walls and the fact that turning consumes energy (required to score 18+). The return
	 * value of this method determines the expected effect of <code>moveNextTo</code> in the test suite.
	 * 
	 * This method must return either 0 or 1.
	 */
	@Override
	public int isMoveNextTo18Plus() {
		return 1;
	}

	/**
	 * Move a given robot as close as possible (expressed as the manhattan distance) to second given robot. 
	 * 	If multiple optimal (in distance) solutions exist, the solution that requires the least amount of total energy is selected.  
	 * 	If both robots are not located on the same board, their states remain unchanged.
	 * 
	 * @param	robot
	 * 			The first robot which is involved in the movement.
	 * @param	other
	 * 			The second robot which is involved in the movement.
	 */
	@Override
	public void moveNextTo(Robot robot, Robot other) {
		try {
			robot.moveNextTo(other);
		} catch(Exception e) {
			System.err.println("Exception: " + e.getMessage());
		}
	}

	/**
	 * Make a given robot shoot in the orientation it is currently facing (if possible).
	 * 
	 * @param	robot
	 * 			The robot which should shoot.
	 * @effect	...
	 * 			| if(! robot.getEnergy().lessThan(robot.getEnergyRequiredToShoot()))
	 *			|	then robot.shoot()
	 */
	@Override
	public void shoot(Robot robot) throws UnsupportedOperationException {
		if(! robot.getEnergy().lessThan(robot.getEnergyRequiredToShoot()))
			robot.shoot();
	}

	/**
	 * Create a new wall.
	 * 
	 * @return	...
	 * 			| result == new Wall()
	 */
	@Override
	public Wall createWall() throws UnsupportedOperationException {
		return new Wall();
	}

	/**
	 * Put a given wall at a given position, on a given board (if possible).
	 * 
	 * @param	board
	 * 			The board to put the wall on.
	 * @param	x
	 * 			The x-coordinate of the position to put the wall at.
	 * @param	y
	 * 			The y-coordinate of the position to put the wall at.
	 * @param	wall
	 * 			The wall to put on a given position, on a given board.
	 * @effect	...
	 * 			| board.putElement(new Position(x,y), battery)
	 */
	@Override
	public void putWall(Board board, long x, long y, Wall wall) throws UnsupportedOperationException {
		try {
			board.putElement(new Position(x,y), wall);
		} catch (IllegalArgumentException e) {
			System.err.println(e.getMessage());
		} catch (InvalidPositionException e) {
			System.err.println(e.getMessage());
		} catch (PositionSharingException e) {
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Return the x-coordinate of a given wall.
	 * 
	 * @param 	wall
	 * 			The wall which x-coordinate should be returned.
	 * @return 	...
	 * 			| if(wall.getPosition() != null)
	 * 			|	then result == wall.getPosition().getX()
	 * 			| else
	 * 			|	then result == -1
	 */
	@Override
	public long getWallX(Wall wall) throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException {
		if(wall.getPosition() == null) {
			System.err.println("The wall is not placed on a board!");
			return -1;
		}
		return wall.getPosition().getX();
	}

	/**
	 * Return the y-coordinate of a given wall.
	 * 
	 * @param 	wall
	 * 			The wall which y-coordinate should be returned.
	 * @return 	...
	 * 			| if(wall.getPosition() != null)
	 * 			|	then result == wall.getPosition().getY()
	 * 			| else
	 * 			|	then result == -1
	 */
	@Override
	public long getWallY(Wall wall) throws IllegalArgumentException, IllegalStateException, UnsupportedOperationException {
		if(wall.getPosition() == null) {
			System.err.println("The wall is not placed on a board!");
			return -1;
		}
		return wall.getPosition().getY();
	}

	/**
	 * Return a set containing all robots on a given board.
	 * 
	 * @param	board
	 * 			The board from which a set of all robots it contains should be returned.
	 * @return	...
	 * 			| board.getElements(Robot.class)
	 */
	@Override
	public Set<Robot> getRobots(Board board) {
		return board.getElements(Robot.class);
	}

	/**
	 * Return a set containing all batteries on a given board.
	 * 
	 * @param	board
	 * 			The board from which a set of all batteries it contains should be returned.
	 * @return	...
	 * 			result == board.getElements(Battery.class)
	 */
	@Override
	public Set<Battery> getBatteries(Board board) {
		return board.getElements(Battery.class);
	}

	/**
	 * Return a set containing all walls on a given board.
	 * 
	 * @param	board
	 * 			The board from which a set of all walls it contains should be returned.
	 * @return	...
	 * 			| result == board.getElements(Wall.class)
	 */
	@Override
	public Set<Wall> getWalls(Board board) throws UnsupportedOperationException {
		return board.getElements(Wall.class);
	}

}
